var Typeahead = {
    /**
     * cached data for later used.
     */
    cached : {
    },
    data : {
    },
    link : {

    },
    init : function(ele, opts) {
        if (ele.attr('typeahead-init')) {
            return;
        }
        ele.attr('typeahead-init', '1');
        Typeahead.create(ele, opts);
    },
    create : function(ele, newOpts) {
        /**
         * waiting time from keydown to process text
         */
        var time = 20;
        /**
         *timeout id
         */
        var timeId = 0;

        /**
         *  token position
         */
        var tokenPos = 0;

        /**
         * token string
         */
        var tokenStr = '';

        /**
         * input element
         */
        var input = $(ele);

        /**
         * closed
         */
        var container = input.closest('.uiMentionTypeAheadContainer');

        /**
         * dom
         */
        var hidden = container.find('.uiMentionHidden');

        /**
         *dom
         */
        var hightlight = container.find('.uiMentionHighlighter');

        /**
         * ac list is hidding
         */
        var isHide = true;

        /**
         * ul list
         */
        var ul = 0;

        /**
         * replace element.
         */
        var reg2 = /@(\[\w+\.\d+):([^\]]+)(\])/gi;

        /**
         * searchLink regular
         */
        var linkReg = /(http|https):\/\/(\w|\-|\_)+(\.\w{2,})+([\w#!:.?+=&%@!\-\/])*(\s|\n)/i;

        /**
         * tagged friends
         */
        var tags = {
        };

        var attatchedLink = false;

        var opts = $.extend({
            min : 0,
            max : 10,
            onSubmit : false,
            deltaHeight : 0,
            minHeight : 20,
            remoteUrl : _config.basePath + 'typeahead.php',
            maxDisplay : 10,
            onParseLink : false
        }, newOpts);

        /**
         * escape regular expression
         */
        function escape(text) {
            return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
        }

        function filterData(keyword) {
            var rows = new Array();
            var length = Typeahead.data.length;
            var taken = keyword.substr(1).replace(/$/g, '').split(/\s+/g);

            for (var i = 0; i < taken.length; ++i) {
                taken[i] = escape(taken[i]);
            }

            var num = taken.length;
            var reg = new RegExp('(^|\\s+)(' + taken.join('|') + ')', 'gi');

            console.log(reg);

            for (var i = 0; i < length && rows.length <= 30; i++) {
                var obj = Typeahead.data[i];
                var match = obj.text.match(reg);
                console.log(match);

                if (match && match.length >= num) {
                    rows.push(obj);
                }
            }

            var ret = {
                'q' : keyword,
                'rows' : rows
            }
            return ret;
        }

        /**
         * create ul dom
         */
        function createUl() {
            ul = $('#uiAcList');
            if (ul.length == 0) {
                ul = $('<ul class="uiAcList" id="uiAcList">').appendTo('body');
            }
        }

        /**
         * hide ul list
         */
        function hideUl() {
            isHide = true;
            ul && ul.hide();
        }

        /**
         *show
         */
        function showUl() {
            var offset = input.offset();
            ul.css({
                left : offset.left,
                top : offset.top + input.height(),
                width : input.width()
            }).show();
            isHide = false;
        }

        /**
         * move list up
         */
        function goUp() {
            var e = ul.find('li.active');
            e = e.prev();

            if (e.length == 0) {
                e = ul.find('li.item:first');
            }
            ul.find('li').removeClass('active');
            e.addClass('active');
        }

        /**
         * move list down
         */
        function goDown() {
            var e = ul.find('li.active');
            e = e.next();

            if (e.length == 0) {
                e = ul.find('li.item:first');
            }
            ul.find('li').removeClass('active');
            e.addClass('active');
        }

        /**
         * get current active item
         */
        function getActiveLi() {
            if (isHide) {
                return false;
            }
            var e = ul.find('li.active');

            if (e.length == 0) {
                e = ul.find('.item:first');
            }

            if (e.length == 0) {
                return false;
            }

            return e;
        }

        /**
         * create item
         * @param Object {type, id, text, photo}
         */
        function createLi(obj) {
            var html = '<img class="lfloat mright5" width="32" height="32" src="' + obj.photo + '" />' + obj.text + '';
            var li = $('<li class="clearfix item">').appendTo(ul);
            li.html(html);
            li.bind('mousedown', function(evt) {
                selectHandle(obj);
            }).bind('mouseover', function(evt) {
                $(this).addClass('active');
            }).bind('mouseout', function(evt) {
                $(this).removeClass('active');
            });
        }

        function createList(data) {
            createUl();
            ul.html('');
            Typeahead.cached[data.q] = data;

            if (data.q != tokenStr) {
                hideUl();
                return;
            }

            var count = 0;
            for (var i = 0; i < data.rows.length; ++i) {
                var o = data.rows[i];
                var id = o.type + '.' + o.id;
                if ( typeof tags[id] != 'undefined')
                    continue;
                createLi(data.rows[i]);
                if (++count > opts.maxDisplay) {
                    break;
                };
            }

            /**
             *check if empty content.
             */
            if (!count) {
                hideUl();
            } else {
                showUl();
            }

        }

        /**
         * pre process
         */
        function processInput() {
            /**
             * check caret pos
             */
            caretPos = input.caret().start;

            var val = input.val();
            /**
             * get query string
             */
            var q = val.substr(0, caretPos);

            if (opts.onParseLink && !attatchedLink) {
                var match = val.match(linkReg);

                if (match) {
                    var url = match[0].replace(/\s|\n/g, '');

                    input.trigger('parseLink', {
                        'url' : url
                    });
                }
            }

            /**
             * get token pos
             */
            tokenPos = q.lastIndexOf('@');

            if (tokenPos < 0) {
                return '';
            };

            q = q.substr(tokenPos, caretPos);

            /**
             * do not check if token string is the same
             */
            if (q == tokenStr) {
                return;
            }

            /**
             * apply new tokenStr to check
             */
            tokenStr = q;

            if (tokenStr == '') {
                return;
            }

            if (tokenStr.length < opts.min || tokenStr.length > opts.max) {
                hideUl();
                return;
            }
            /**
             *check in cached
             */
            if ( typeof Typeahead.cached[tokenStr] == 'object') {
                createList(Typeahead.cached[tokenStr]);
                return;
            }

            if (opts.remoteUrl) {
                /**
                 * get data from remote
                 */
                $.getJSON(opts.remoteUrl, {
                    'q' : tokenStr
                }, createList);
            } else {
                createList(filterData(tokenStr));
            }

        }

        var oldText = '';

        /**
         * update hidden val
         */
        function updateHidden(val) {
            val = val.replace('﻿', '');
            var html = val.replace(reg2, '<b>$2</b>').replace(/\n/gmi, '<br />')

            hidden.val(val);
            hightlight.html(html + '  ');

            var newVal = val.replace(reg2, '$2﻿');
            var caret = input.caret().start;

            var h1 = hightlight.height();
            var h2 = input.height();

            if (h1 > opts.minHeight) {
                input.css({
                    'height' : h1 + opts.deltaHeight
                });
            } else if (h2 > opts.minHeight && h2 > h1) {
                input.css({
                    'height' : opts.minHeight
                });
            }

            if (oldText && oldText != newVal) {
                input.val(newVal);
                //input.caret(caret, caret);

            }
            oldText = newVal;
        }

        var isProcessing = false;

        /**
         * var
         */
        function processHidden() {
            if (isProcessing) {
                return;
            }

            isProcessing = true;

            var of = input.caret();
            var val = input.val();

            for (var i in tags) {
                var tag = tags[i];
                /**
                 * found tag,now replace by token
                 */
                var len = tag.text.length;
                var pos = val.search(tag.text);
                if (pos > -1) {
                    val = val.substr(0, pos) + tag.token + val.substr(pos + len);
                } else {
                    delete (tags[i]);
                }
            }
            updateHidden(val);

            isProcessing = false;
        }

        function selectHandle(obj) {
            hideUl();
            var pos1 = tokenPos;
            var pos2 = input.caret().start;
            var text = obj.text + '﻿';
            var newCaretPos = pos1 + text.length + 1;

            /**
             * update input val
             */
            var val = input.val();
            val = val.substr(0, pos1) + text + ' ' + val.substr(pos2);
            input.val(val);
            input.caret(newCaretPos, newCaretPos);
            input.focus();

            /**
             * update hidden val
             */
            var id = obj.type + '.' + obj.id;
            var token = '@[' + id + ':' + obj.text + ']';
            tags[id] = {
                'text' : text,
                'text2' : obj.text,
                'token' : token
            };
            processHidden();
        }

        /**
         *
         */
        function keyupHandle(evt) {
            if (evt && evt.keyCode) {
                var keyCode = evt.keyCode;
                if (13 == keyCode || 9 == keyCode) {
                    //	return ;
                }

            }
            processInput();
            //	s.length > 0 ? getFriends(s) : ac.hide();
            timeId = window.setTimeout(processHidden, time);
        }

        /**
         * handle event keydown
         * @param Event evt
         * @return void
         */
        function keydownHandle(evt) {

            if (evt && evt.keyCode) {

                var keyCode = evt.keyCode
                if (!evt.shiftKey && !evt.altKey && !evt.ctrlKey) {

                    if (false == isHide) {
                        if (38 == keyCode) {
                            // move up
                            evt.preventDefault();
                            goUp();
                        } else if (40 == keyCode) {
                            // move down
                            evt.preventDefault();
                            goDown();
                        } else if (13 == keyCode || 9 == keyCode) {
                            evt.preventDefault();
                            // enter
                            var li = getActiveLi();
                            if (li) {
                                li.trigger('mousedown');
                            }
                        } else if (9 == keyCode || 27 == keyCode) {
                            //escape
                            evt.preventDefault();
                            hideUl();

                        } else if (37 == keyCode) {

                        } else if (39 == keyCode) {

                        }
                    } else {
                        if (13 == keyCode && opts.onSubmit) {
                            evt.preventDefault();
                            opts.onSubmit(input);
                        }
                    }
                    //console.log(keyCode);
                    return;
                }
            }
        }

        function clickHandle() {
            ul && hideUl();
        }

        /**
         *
         * @param {Object} evt
         * @param {Object} param
         */
        function parseLinkHandle(evt, param) {
            attatchedLink = true;
            opts.onParseLink(input, param);
        }

        /**
         *
         * @param {Object} evt
         * @param {Object} param
         */
        function parseLinkChanged(evt, param) {
            attatchedLink = param.attatchedLink;
        }

        function focus(evt) {
            if (input.val() == input.attr('placeholder')) {
                input.val('');
            }
        }


        hightlight.css({
            'width' : input.width()
        });

        input.bind('keyup', keyupHandle).bind('keydown', keydownHandle).bind('paste', processHidden).bind('cut', processHidden).bind('parseLink', parseLinkHandle).bind('changed', processHidden).bind('change', processHidden);
        $(document).bind('click', clickHandle);
    }
};
